/*
 * class code is from  eclipse wiki plugin  (License: Free CPL)
 * see http://eclipsewiki.sourceforge.net/
 */

package ws.ebel.idewiki.editors;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.PackageDeclarationMatch;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.core.search.TypeDeclarationMatch;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.swt.graphics.Image;
import org.eclipse.team.internal.ccvs.core.ICVSResource;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;

import ws.ebel.idewiki.Activator;
import ws.ebel.idewiki.IdeWikiConstants;
import ws.ebel.idewiki.util.CommonHelper;

/**
 * The Class JavaCompletionProcessor.
 */
public final class JavaCompletionProcessor {

    /** The Constant IJAVA_ELEMENT_COMPARATOR. */
    public static final Comparator IJAVA_ELEMENT_COMPARATOR = new Comparator() {
        public int compare(Object o1, Object o2) {
            IJavaElement first = (IJavaElement) o1;
            IJavaElement second = (IJavaElement) o2;
            int typeName = first.getElementName().compareTo(second.getElementName());
            if (typeName != 0) {
                return typeName;
            }
            return first.getElementName().compareTo(second.getElementName());
        }
    };

    /** The project. */
    private IJavaProject project;
    
    /** The proposals. */
    private ArrayList proposals;

    /** The include packages. */
    private boolean includePackages;

    /**
     * The Constructor.
     */
    public JavaCompletionProcessor() {
        proposals = new ArrayList();
    }

    /**
     * Gets the proposals.
     * 
     * @param viewer the viewer
     * @param project the project
     * @param documentOffset the document offset
     * 
     * @return the proposals
     * 
     * @throws BadLocationException the bad location exception
     */
    public ArrayList getProposals(IJavaProject project, ITextViewer viewer, int documentOffset) throws BadLocationException {
        includePackages = false;
        proposals.clear();

        this.project = project;
        String text = getFullyQualifiedTypePrefix(viewer, documentOffset);
        if (text == null) {
            return proposals;
        }
        try {
            IPackageFragment[] packages = getMatchingPackageFragments(text);
            IType[] types = getMatchingTypes(packages, text);
            buildProposals(types, text, documentOffset);
            if (includePackages) {
                buildProposals(packages, text, documentOffset);
            }
        } catch (CoreException e) {
            Activator.getDefault().logAndReport("Completion Error", e.getLocalizedMessage(), e);
        }
        return proposals;
    }

    /**
     * Builds the proposals.
     * 
     * @param text the text
     * @param types the types
     * @param documentOffset the document offset
     * 
     * @throws JavaModelException the java model exception
     */
    private void buildProposals(IType[] types, String text, int documentOffset) throws JavaModelException {
        TreeMap sortedProposals = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        for (int i = 0; i < types.length; i++) {
            String matchName = types[i].getFullyQualifiedName();
            
            if(Activator.getDefault().getIsWebCvsEnabled()){
                matchName = matchName + getCvsRootAppendix(types[i].getResource(), matchName);
            }
            
            String display = getDisplayString(types[i]);
            ICompletionProposal proposal = new CompletionProposal(matchName, documentOffset - text.length() - 1, text.length(), matchName.length(), getIcon(types[i]), display, null, null);
            sortedProposals.put(matchName, proposal);
        }
        proposals.addAll(sortedProposals.values());
    }

    /**
     * @param aResource
     * @return
     */
    private String getCvsRootAppendix(IResource aResource, String aFullQualifiedName) {
        String result = "";
        ICVSResource cvsRes = CVSWorkspaceRoot.getCVSResourceFor(aResource);
        
        try {
            String remotePath = cvsRes.getRemoteLocation(cvsRes.getParent());
            if(remotePath != null && !remotePath.startsWith("null") && !aFullQualifiedName.contains("$")){
                String projectName = aResource.getProject().getName();
                String cvsRoot = remotePath.substring(0, remotePath.indexOf(projectName) - 1);
                int start = remotePath.indexOf(projectName) + projectName.length();
                int end = remotePath.indexOf(aFullQualifiedName.replace(".", "/"));
                String srcFolderName = remotePath.substring(start, end);
                
                result = "|cvsRoot=" + cvsRoot + "|srcFolder=" + srcFolderName;
            }
        } catch (Exception e) {
            Activator.getDefault().logAndReport("CVS Exception", "Can't retrieve cvs root of resource!", e);
            result = "";
        }
        return result;
    }

    /**
     * Gets the icon.
     * 
     * @param type the type
     * 
     * @return the icon
     * 
     * @throws JavaModelException the java model exception
     */
    private Image getIcon(IType type) throws JavaModelException {
        Image icon = type.isInterface() ? Activator.getDefault().getImageRegistry().get(IdeWikiConstants.INTERFACE_ICON)
                : Activator.getDefault().getImageRegistry().get(IdeWikiConstants.CLASS_ICON);
        return icon;
    }

    /**
     * Gets the display string.
     * 
     * @param type the type
     * 
     * @return the display string
     */
    private String getDisplayString(IType type) {
        String suffix;
        if (type.getPackageFragment().isDefaultPackage()) {
            suffix = " - (default package)";
        } else {
            suffix = " - " + type.getPackageFragment().getElementName();
        }
        String display = type.getElementName() + suffix;
        return display;
    }

    /**
     * Builds the proposals.
     * 
     * @param packages the packages
     * @param text the text
     * @param documentOffset the document offset
     */
    private void buildProposals(IPackageFragment[] packages, String text, int documentOffset) {
        TreeMap sortedProposals = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        for (int i = 0; i < packages.length; i++) {
            String matchName = packages[i].getElementName();
            ICompletionProposal proposal = new CompletionProposal(matchName, documentOffset - text.length() - 1, text.length(), matchName.length(), Activator.getDefault().getImageRegistry().get(
                    IdeWikiConstants.PACKAGE_ICON), null, null, null);
            sortedProposals.put(matchName, proposal);
        }
        proposals.addAll(sortedProposals.values());
    }

    /**
     * Gets the matching types.
     * 
     * @param text the text
     * @param fragments the fragments
     * 
     * @return the matching types
     * 
     * @throws CoreException the core exception
     */
    private IType[] getMatchingTypes(IPackageFragment[] fragments, String text) throws CoreException {
        final Set matches = new TreeSet(JavaCompletionProcessor.IJAVA_ELEMENT_COMPARATOR);
        SearchRequestor requestor = new SearchRequestor() {
            public void acceptSearchMatch(SearchMatch match) throws CoreException {
                if (match instanceof TypeDeclarationMatch) {
                    TypeDeclarationMatch tdm = (TypeDeclarationMatch) match;
                    matches.add(tdm.getElement());
                }
            }
        };
        IJavaElement[] elements;
        if (fragments.length == 0) {
            elements = new IJavaElement[] { project };
        } else {
            elements = fragments;
        }
        String prefix = text;
        if (text.indexOf('.') > 0) {
            prefix = text.substring(text.lastIndexOf('.') + 1);
        }
        if (prefix.length() == 0) {
            return getTypesInPackages(fragments);
        }
        IJavaSearchScope scope = SearchEngine.createJavaSearchScope(elements, IJavaSearchScope.SOURCES);
        SearchEngine searchEngine = new SearchEngine();
        SearchPattern pattern = SearchPattern.createPattern(prefix, IJavaSearchConstants.TYPE,
                IJavaSearchConstants.DECLARATIONS, SearchPattern.R_PREFIX_MATCH);
        searchEngine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, scope,
                requestor, null);
        
        IType[] result = (IType[]) matches.toArray(new IType[matches.size()]);
                
        return result;
    }

    /**
     * Gets the types in packages.
     * 
     * @param fragments the fragments
     * 
     * @return the types in packages
     * 
     * @throws JavaModelException the java model exception
     */
    private IType[] getTypesInPackages(IPackageFragment[] fragments) throws JavaModelException {
        includePackages = false;
        HashSet types = new HashSet();
        for (int i = 0; i < fragments.length; i++) {
            addTypesInPackage(fragments[i], types);
        }
        return (IType[]) types.toArray(new IType[types.size()]);
    }

    /**
     * Adds the types in package.
     * 
     * @param types the types
     * @param fragment the fragment
     * 
     * @throws JavaModelException the java model exception
     */
    private void addTypesInPackage(IPackageFragment fragment, HashSet types) throws JavaModelException {
        IJavaElement[] children = fragment.getChildren();
        for (int i = 0; i < children.length; i++) {
            if (children[i].getElementType() == IJavaElement.COMPILATION_UNIT) {
                ICompilationUnit compilationUnit = (ICompilationUnit) children[i];
                types.addAll(Arrays.asList(compilationUnit.getTypes()));
            }
        }
    }

    /**
     * getMatchingPackageFragments.
     * 
     * @param text the text
     * 
     * @return the matching package fragments
     * 
     * @throws CoreException the core exception
     */
    private IPackageFragment[] getMatchingPackageFragments(final String text) throws CoreException {
        String prefix = text;
        if (text.endsWith(".")) {
            prefix = text.substring(0, text.length() - 1);
        }
        final Set fragments = new TreeSet(JavaCompletionProcessor.IJAVA_ELEMENT_COMPARATOR);
        SearchRequestor requestor = new SearchRequestor() {
            public void acceptSearchMatch(SearchMatch match) throws CoreException {
                if (match instanceof PackageDeclarationMatch) {
                    PackageDeclarationMatch tdm = (PackageDeclarationMatch) match;
                    fragments.add(tdm.getElement());
                }
            }
        };
        IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaElement[] { project }, IJavaSearchScope.SOURCES);
        //IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
        SearchEngine searchEngine = new SearchEngine();
        SearchPattern pattern = SearchPattern.createPattern(prefix, IJavaSearchConstants.PACKAGE,
                IJavaSearchConstants.DECLARATIONS, SearchPattern.R_PREFIX_MATCH);
        searchEngine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, scope,
                requestor, null);
        return (IPackageFragment[]) fragments.toArray(new IPackageFragment[fragments.size()]);
    }

    /**
     * Get the (start of) the fully qualified type at <code>documentOffset</code> that the user is trying to complete.
     * 
     * @param viewer the viewer
     * @param documentOffset the document offset
     * 
     * @return the beginnings of the fully qualified type or <code>null</code> if there is nothing.
     * 
     * @throws BadLocationException the bad location exception
     */
    private String getFullyQualifiedTypePrefix(ITextViewer viewer, int documentOffset) throws BadLocationException {
        IDocument document = viewer.getDocument();
        int characterIndex = documentOffset - 2;
        char indexChar = document.getChar(characterIndex);
        if (characterIndex < 0 || !CommonHelper.isJavaClassNamePart(indexChar)) {
            return null;
        }
        int start = characterIndex;
        while (start > 0 && CommonHelper.isJavaClassNamePart(document.getChar(start))) {
            start--;
        }
        while (start < characterIndex && !Character.isJavaIdentifierPart(document.getChar(start))) {
            start++;
        }
        String prefix = document.get(start, characterIndex - start + 1);
        if (".".equals(prefix)) {
            return null;
        }
        return prefix;
    }

}